#include <boost/make_shared.hpp>
#include <pcl/point_types.h>
#include <pcl/point_cloud.h>
#include <pcl/point_representation.h>
#include <pcl/io/pcd_io.h>
#include <pcl/filters/voxel_grid.h>
#include <pcl/filters/filter.h>
#include <pcl/features/normal_3d.h>
#include <pcl/registration/icp.h>
#include <pcl/registration/icp_nl.h>
#include <pcl/registration/transforms.h>
#include <pcl/visualization/pcl_visualizer.h>

using pcl::visualization::PointCloudColorHandlerGenericField;
using pcl::visualization::PointCloudColorHandlerCustom;

typedef pcl::PointXYZ PointT;
typedef pcl::PointCloud<PointT> PointCloud;
typedef pcl::PointNormal PointNormalT;
typedef pcl::PointCloud<PointNormalT> PointCloudWithNormals;

using namespace std;

pcl::visualization::PCLVisualizer *p; //创建可视化工具
int vp_1, vp_2;                       //定义左右视点

/* 处理点云的方便的结构定义 */
struct PCD
{
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud; // 点云文件的结构体
    std::string f_name; // 点云文件的名字
    PCD() : cloud (new pcl::PointCloud<pcl::PointXYZ>) {}; 
};

struct PCDComparator
{
    bool operator () (const PCD& p1, const PCD& p2)
    {
        return (p1.f_name < p2.f_name);
    }
};

/* 以< x, y, z, curvature >形式定义一个新的点 */
class MyPointRepresentation : public pcl::PointRepresentation <PointNormalT>
{
    using pcl::PointRepresentation<PointNormalT>::nr_dimensions_;

public:
    MyPointRepresentation ()
    {
        nr_dimensions_ = 4;   //定义点的维度
    }

    /* 覆盖copyToFloatArray方法来定义我们的特征矢量，将点转成4维数组 */
    virtual void copyToFloatArray (const PointNormalT &p, float * out) const
    {
        /* < x, y, z, curvature > */
        out[0] = p.x;
        out[1] = p.y;
        out[2] = p.z;
        out[3] = p.curvature;
    }
};


/* 在可视化窗口的第一视点显示源点云和目标点云 */
void showCloudsLeft(const PointCloud::Ptr cloud_target, const PointCloud::Ptr cloud_source)
{
    p->removePointCloud ("vp1_target");
    p->removePointCloud ("vp1_source");
    PointCloudColorHandlerCustom<PointT> tgt_h (cloud_target, 0, 255, 0); // 绿色为目标点云
    PointCloudColorHandlerCustom<PointT> src_h (cloud_source, 255, 0, 0); // 红色为源点云
    p->addPointCloud (cloud_target, tgt_h, "vp1_target", vp_1);
    p->addPointCloud (cloud_source, src_h, "vp1_source", vp_1);
    PCL_INFO ("Press q to begin the registration.\n");
    p-> spin();
}


/* 在可视化窗口的第二视点显示源点云和目标点云 */
void showCloudsRight(const PointCloudWithNormals::Ptr cloud_target, const PointCloudWithNormals::Ptr cloud_source)
{
    p->removePointCloud ("source");
    p->removePointCloud ("target");
    PointCloudColorHandlerGenericField<PointNormalT> tgt_color_handler (cloud_target, "curvature");
    if (!tgt_color_handler.isCapable ())
    {
        PCL_WARN ("Cannot create curvature color handler!");
    }
    PointCloudColorHandlerGenericField<PointNormalT> src_color_handler (cloud_source, "curvature");
    if (!src_color_handler.isCapable ())
    {
        PCL_WARN ("Cannot create curvature color handler!");
    }
    p->addPointCloud (cloud_target, tgt_color_handler, "target", vp_2);
    p->addPointCloud (cloud_source, src_color_handler, "source", vp_2);
    p->spinOnce();
}


/** 加载一组我们想要匹配在一起的PCD文件
  * argc: 是参数的数量 (pass from main ())
  * argv: 实际的命令行参数 (pass from main ())
  * models: 点云数据集的合成矢量
  */
void loadData (int argc, char **argv, std::vector<PCD, Eigen::aligned_allocator<PCD> > &models)
{
    std::string extension (".pcd");
    /* 假定第一个参数是实际测试模型 */
    for (int i = 1; i < argc; i++)
    {
        std::string fname = std::string (argv[i]);
        if (fname.size () <= extension.size ())
        {
            continue;
        }

        std::transform (fname.begin (), fname.end (), fname.begin (), (int(*)(int))tolower);
        
        /* 检查参数是一个pcd文件 */
        if (fname.compare (fname.size () - extension.size (), extension.size (), extension) == 0)
        {
            PCD m;
            m.f_name = argv[i];
            pcl::io::loadPCDFile (argv[i], *m.cloud);
            std::vector<int> indices;
            pcl::removeNaNFromPointCloud(*m.cloud,*m.cloud, indices); //从点云中移除NAN点
            models.push_back (m);
        }
    }
}


/**匹配一对点云数据集并且返回结果
  *cloud_src：源点云
  *cloud_tgt：目标点云
  *output：输出的配准结果的源点云
  *final_transform：源点云和目标点云之间的转换矩阵
  */
void pairAlign (const PointCloud::Ptr cloud_src, const PointCloud::Ptr cloud_tgt, PointCloud::Ptr output, Eigen::Matrix4f &final_transform, bool downsample = false)
{
    /* 下采样 */
    PointCloud::Ptr src (new PointCloud); // 存储滤波后的源点云
    PointCloud::Ptr tgt (new PointCloud); // 存储滤波后的目标点云
    pcl::VoxelGrid<PointT> grid; // 滤波处理对象
    if (downsample)
    {
        grid.setLeafSize (0.05, 0.05, 0.05); // 设置滤波处理时采用的体素大小
        grid.setInputCloud (cloud_src);
        grid.filter (*src);
        grid.setInputCloud (cloud_tgt);
        grid.filter (*tgt);
    }
    else
    {
        src = cloud_src;
        tgt = cloud_tgt;
    }

    /* 计算曲面法线和曲率 */
    PointCloudWithNormals::Ptr points_with_normals_src (new PointCloudWithNormals);
    PointCloudWithNormals::Ptr points_with_normals_tgt (new PointCloudWithNormals);
    pcl::NormalEstimation<PointT, PointNormalT> norm_est; // 点云法线估计对象
    pcl::search::KdTree<pcl::PointXYZ>::Ptr tree (new pcl::search::KdTree<pcl::PointXYZ> ());
    norm_est.setSearchMethod (tree); // 设置估计对象采用的搜索对象
    norm_est.setKSearch (30); // 设置估计时进行搜索用的k数
    norm_est.setInputCloud (src); 
    norm_est.compute (*points_with_normals_src); // 下面分别估计源和目标点云法线
    pcl::copyPointCloud (*src, *points_with_normals_src);
    norm_est.setInputCloud (tgt);
    norm_est.compute (*points_with_normals_tgt);
    pcl::copyPointCloud (*tgt, *points_with_normals_tgt);

    MyPointRepresentation point_representation;
    //调整'curvature'尺寸权重以便使它和x, y, z平衡
    float alpha[4] = {1.0, 1.0, 1.0, 1.0};
    point_representation.setRescaleValues (alpha);

    /* 配准 */
    pcl::IterativeClosestPointNonLinear<PointNormalT, PointNormalT> reg; // 配准对象
    reg.setTransformationEpsilon (1e-6); // 设置收敛判断条件，越小精度越大，收敛也越慢
    reg.setMaxCorrespondenceDistance (0.1); //将两个对应关系之间的(src<->tgt)最大距离设置为10厘米（大于此值的点对不考虑），需要根据数据集大小来调整
    reg.setPointRepresentation (boost::make_shared<const MyPointRepresentation> (point_representation));  //设置点表示
    reg.setInputCloud (points_with_normals_src); // 设置源点云
    reg.setInputTarget (points_with_normals_tgt); // 设置目标点云

    /* 在一个循环中运行相同的最优化并且使结果可视化 */
    Eigen::Matrix4f Ti = Eigen::Matrix4f::Identity (), prev, targetToSource;
    PointCloudWithNormals::Ptr reg_result = points_with_normals_src;
    reg.setMaximumIterations (2); // ICP内部计算时，设置最大迭代次数

    // 手动迭代，本例是30次
    for (int i = 0; i < 30; ++i)
    {
        PCL_INFO ("Iteration Nr. %d.\n", i);
        points_with_normals_src = reg_result;         //为了可视化的目的保存点云
        reg.setInputCloud (points_with_normals_src);
        reg.align (*reg_result);
        Ti = reg.getFinalTransformation () * Ti;      //在每一个迭代之间累积转换

        /* 如果这次转换和之前转换之间的差异小于阈值，则通过减小最大对应距离来改善程序 */
        if (fabs ((reg.getLastIncrementalTransformation () - prev).sum ()) < reg.getTransformationEpsilon ())
        {
            reg.setMaxCorrespondenceDistance (reg.getMaxCorrespondenceDistance () - 0.001);
            prev = reg.getLastIncrementalTransformation ();
        }

        showCloudsRight(points_with_normals_tgt, points_with_normals_src);  //可视化当前最新状态
    }

    // 打印转换矩阵，要确定下下面三个变换矩阵哪个是所需要的
    std::cout << reg.getFinalTransformation() << std::endl;
    std::cout << "=======" << std::endl;
    std::cout << Ti << std::endl;

    targetToSource = Ti.inverse();  //得到目标点云到源点云的变换

    std::cout << "=======\n得到目标点云到源点云的变换" << std::endl;
    std::cout << targetToSource << std::endl;

    /* 把目标点云转换回源框架 */
    pcl::transformPointCloud (*cloud_tgt, *output, targetToSource);
    p->removePointCloud ("source");
    p->removePointCloud ("target");
    PointCloudColorHandlerCustom<PointT> cloud_tgt_h (output, 0, 255, 0);
    PointCloudColorHandlerCustom<PointT> cloud_src_h (cloud_src, 255, 0, 0);
    p->addPointCloud (output, cloud_tgt_h, "target", vp_2);
    p->addPointCloud (cloud_src, cloud_src_h, "source", vp_2);
    PCL_INFO ("Press q to continue the registration.\n");
    p->spin ();
    p->removePointCloud ("source"); 
    p->removePointCloud ("target");

    /* 添加源点云到转换目标 */
    *output += *cloud_src;
    final_transform = targetToSource;
}

int main (int argc, char** argv)
{
    /* 加载数据 */
    std::vector<PCD, Eigen::aligned_allocator<PCD> > data; // 存储管理所有打开的点云
    loadData (argc, argv, data); // 加载所有点云文件到data中

    /* 检查用户输入 */
    if (data.empty ())
    {
        PCL_ERROR ("Syntax is: %s <source.pcd> <target.pcd> [*]", argv[0]);
        PCL_ERROR ("[*] - multiple files can be added. The registration results of (i, i+1) will be registered against (i+2), etc");
        PCL_INFO ("Example: %s `rospack find pcl`/test/bun0.pcd `rospack find pcl`/test/bun4.pcd", argv[0]);
        return (-1);
    }

    PCL_INFO ("Loaded %d datasets.", (int)data.size ());

    /* 创建一个PCL可视化对象 */
    p = new pcl::visualization::PCLVisualizer (argc, argv, "Pairwise Incremental Registration example");
    p->createViewPort (0.0, 0, 0.5, 1.0, vp_1); // 用左半窗口创建视图 vp_1
    p->createViewPort (0.5, 0, 1.0, 1.0, vp_2); // 用右半窗口创建视图 vp_2

    PointCloud::Ptr result (new PointCloud), source, target;
    Eigen::Matrix4f GlobalTransform = Eigen::Matrix4f::Identity (), pairTransform;

    // 循环处理所有点云
    for (size_t i = 1; i < data.size (); ++i)
    {
        source = data[i-1].cloud; // 连续配准
        target = data[i].cloud; // 相邻两组点云

        /* 添加可视化数据 */
        showCloudsLeft(source, target); // 可视化为配准的源和目标点云
        PointCloud::Ptr temp (new PointCloud);
        PCL_INFO ("Aligning %s (%d) with %s (%d).\n", data[i-1].f_name.c_str (), source->points.size (), data[i].f_name.c_str (), target->points.size ());
        // 调用子函数完成一组点云的配准，temp返回配准后两组点云在第一组点云坐标下的点云,pair-Transform返回从目标点云target到源点云source的变换举证。
        pairAlign (source, target, temp, pairTransform, true);

        pcl::transformPointCloud (*temp, *result, GlobalTransform); //把当前的两两配对转换到全局变换
        GlobalTransform = pairTransform * GlobalTransform;          //更新全局变换

        /* 保存配准对，转换到第一个点云框架中 */
        std::stringstream ss;
        ss << i << ".pcd";
        pcl::io::savePCDFile (ss.str (), *result, true);
    }
}
